// Copyright 2021 gorse Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"github.com/shima-park/agollo/viper-remote"
	"github.com/spf13/viper"
)

// Config is the configuration for the engine.
type ApolloConfig struct {
	Database  ApolloDatabaseConfig  `mapstructure:"database"`
	Master    ApolloMasterConfig    `mapstructure:"master"`
	Server    ApolloServerConfig    `mapstructure:"server"`
	Recommend ApolloRecommendConfig `mapstructure:"recommend"`
}

// LoadDefaultIfNil loads default settings if config is nil.
func (config *ApolloConfig) LoadDefaultIfNil() *ApolloConfig {
	if config == nil {
		return &ApolloConfig{
			Database:  *(*ApolloDatabaseConfig)(nil).LoadDefaultIfNil(),
			Master:    *(*ApolloMasterConfig)(nil).LoadDefaultIfNil(),
			Server:    *(*ApolloServerConfig)(nil).LoadDefaultIfNil(),
			Recommend: *(*ApolloRecommendConfig)(nil).LoadDefaultIfNil(),
		}
	}
	return config
}

// ApolloDatabaseConfig is the configuration for the database.
type ApolloDatabaseConfig struct {
	DataStore            string   `mapstructure:"data_store"`              // database for data store
	CacheStore           string   `mapstructure:"cache_store"`             // database for cache store
	AutoInsertUser       bool     `mapstructure:"auto_insert_user"`        // insert new users while inserting feedback
	AutoInsertItem       bool     `mapstructure:"auto_insert_item"`        // insert new items while inserting feedback
	CacheSize            int      `mapstructure:"cache_size"`              // cache size for recommended/popular/latest items
	PositiveFeedbackType []string `mapstructure:"positive_feedback_types"` // positive feedback type
	ReadFeedbackType     string   `mapstructure:"read_feedback_type"`      // feedback type for read event
	PositiveFeedbackTTL  uint     `mapstructure:"positive_feedback_ttl"`   // time-to-live of positive feedbacks
	ItemTTL              uint     `mapstructure:"item_ttl"`                // item-to-live of items
}

// LoadDefaultIfNil loads default settings if config is nil.
func (config *ApolloDatabaseConfig) LoadDefaultIfNil() *ApolloDatabaseConfig {
	if config == nil {
		return &ApolloDatabaseConfig{
			AutoInsertUser: true,
			AutoInsertItem: true,
			CacheSize:      100,
		}
	}
	return config
}

// ApolloMasterConfig is the configuration for the master.
type ApolloMasterConfig struct {
	Port        int    `mapstructure:"port"`         // master port
	Host        string `mapstructure:"host"`         // master host
	HttpPort    int    `mapstructure:"http_port"`    // HTTP port
	HttpHost    string `mapstructure:"http_host"`    // HTTP host
	NumJobs     int    `mapstructure:"n_jobs"`       // number of working jobs
	MetaTimeout int    `mapstructure:"meta_timeout"` // cluster meta timeout (second)
}

// LoadDefaultIfNil loads default settings if config is nil.
func (config *ApolloMasterConfig) LoadDefaultIfNil() *ApolloMasterConfig {
	if config == nil {
		return &ApolloMasterConfig{
			Port:        8086,
			Host:        "127.0.0.1",
			HttpPort:    8088,
			HttpHost:    "127.0.0.1",
			NumJobs:     1,
			MetaTimeout: 60,
		}
	}
	return config
}

// ApolloRecommendConfig is the configuration of recommendation setup.
type ApolloRecommendConfig struct {
	PopularWindow            int      `mapstructure:"popular_window"`
	FitPeriod                int      `mapstructure:"fit_period"`
	SearchPeriod             int      `mapstructure:"search_period"`
	SearchEpoch              int      `mapstructure:"search_epoch"`
	SearchTrials             int      `mapstructure:"search_trials"`
	RefreshRecommendPeriod   int      `mapstructure:"refresh_recommend_period"`
	FallbackRecommend        []string `mapstructure:"fallback_recommend"`
	ItemNeighborType         string   `mapstructure:"item_neighbor_type"`
	UserNeighborType         string   `mapstructure:"user_neighbor_type"`
	EnableLatestRecommend    bool     `mapstructure:"enable_latest_recommend"`
	EnablePopularRecommend   bool     `mapstructure:"enable_popular_recommend"`
	EnableUserBasedRecommend bool     `mapstructure:"enable_user_based_recommend"`
	EnableItemBasedRecommend bool     `mapstructure:"enable_item_based_recommend"`
	EnableColRecommend       bool     `mapstructure:"enable_collaborative_recommend"`
}

// LoadDefaultIfNil loads default settings if config is nil.
func (config *ApolloRecommendConfig) LoadDefaultIfNil() *ApolloRecommendConfig {
	if config == nil {
		return &ApolloRecommendConfig{
			PopularWindow:            180,
			FitPeriod:                60,
			SearchPeriod:             180,
			SearchEpoch:              100,
			SearchTrials:             10,
			RefreshRecommendPeriod:   5,
			FallbackRecommend:        []string{"popular"},
			ItemNeighborType:         "auto",
			UserNeighborType:         "auto",
			EnableLatestRecommend:    false,
			EnablePopularRecommend:   false,
			EnableUserBasedRecommend: false,
			EnableItemBasedRecommend: false,
			EnableColRecommend:       true,
		}
	}
	return config
}

// ApolloServerConfig is the configuration for the server.
type ApolloServerConfig struct {
	APIKey   string `mapstructure:"api_key"`   // default number of returned items
	DefaultN int    `mapstructure:"default_n"` // secret key for RESTful APIs (SSL required)
}

// LoadDefaultIfNil loads default settings if config is nil.
func (config *ApolloServerConfig) LoadDefaultIfNil() *ApolloServerConfig {
	if config == nil {
		return &ApolloServerConfig{
			DefaultN: 10,
		}
	}
	return config
}

// FillDefault fill default values for missing values.
func (config *ApolloConfig) FillDefault(viper *viper.Viper) {
	// Default database config
	//defaultDBConfig := *(*ApolloDatabaseConfig)(nil).LoadDefaultIfNil()
	//设置viper默认值
	viper.SetDefault("database", (*ApolloDatabaseConfig)(nil).LoadDefaultIfNil())
	viper.SetDefault("master", *(*ApolloMasterConfig)(nil).LoadDefaultIfNil())
	viper.SetDefault("server", *(*ApolloServerConfig)(nil).LoadDefaultIfNil())
	viper.SetDefault("recommend", *(*ApolloRecommendConfig)(nil).LoadDefaultIfNil())
	viper.Unmarshal(config)

}

// LoadConfig loads configuration from mapstructure file.
func LoadApolloConfig(path string) (*ApolloConfig, *viper.Viper, error) {
	var conf ApolloConfig
	//从 agollo 服务器获取配置
	//	agollo, err := agollo.New("localhost:8080", "your_appid", agollo.AutoFetchOnCacheMiss())
	//	if err != nil {
	//		panic(err)
	//	}
	//	agollo.Start()
	//	conf.FillDefault(agollo)
	remote.SetAppID("monkey-app-recommend")
	v := viper.New()
	v.BindEnv("dev")
	v.SetConfigName("application")
	v.SetConfigType("prop") // 根据namespace实际格式设置对应type
	err := v.AddRemoteProvider("apollo", "192.168.0.1", "application")
	if err != nil {
		panic(err)
	}
	// error handle...
	err = v.ReadRemoteConfig()
	if err != nil {
		panic(err)
	}
	conf.FillDefault(v)
	return &conf, v, nil
}
